package com.globallogic.jira.gadget.servlet;

import java.io.IOException;
import java.io.PrintWriter;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.log4j.Logger;

import com.atlassian.crowd.embedded.api.User;
import com.atlassian.jira.ComponentManager;
import com.atlassian.jira.bc.issue.IssueService;
import com.atlassian.jira.bc.issue.IssueService.TransitionValidationResult;
import com.atlassian.jira.config.ConstantsManager;
import com.atlassian.jira.event.type.EventDispatchOption;
import com.atlassian.jira.issue.Issue;
import com.atlassian.jira.issue.IssueFactory;
import com.atlassian.jira.issue.IssueFieldConstants;
import com.atlassian.jira.issue.IssueInputParameters;
import com.atlassian.jira.issue.IssueInputParametersImpl;
import com.atlassian.jira.issue.IssueManager;
import com.atlassian.jira.issue.MutableIssue;
import com.atlassian.jira.issue.issuetype.IssueType;
import com.atlassian.jira.issue.link.IssueLinkManager;
import com.atlassian.jira.issue.link.IssueLinkType;
import com.atlassian.jira.issue.link.IssueLinkTypeManager;
import com.atlassian.jira.issue.priority.Priority;
import com.atlassian.jira.issue.resolution.Resolution;
import com.atlassian.jira.issue.status.Status;
import com.atlassian.jira.project.ProjectManager;
import com.atlassian.jira.security.JiraAuthenticationContext;
import com.atlassian.jira.user.util.UserUtil;
import com.atlassian.jira.workflow.JiraWorkflow;
import com.globallogic.jira.gadget.composite.IssueAction;
import com.opensymphony.workflow.loader.ActionDescriptor;

/**
 * Servlet that process issue manipulation, that done via context menu in
 * gadget.
 * 
 * @author yegor.chumakov
 * @authot anton.pereverziev
 */

@SuppressWarnings("serial")
public class IssueResolver extends HttpServlet {
	private static final Logger log = Logger.getLogger(IssueResolver.class);
	static final String REGEX = "(\\d+[wdhm])";
	private static final Pattern pattern = Pattern.compile(REGEX);
	private final IssueService is;

	public IssueResolver(final IssueService is) {
		this.is = is;
	}

	@Override
	protected void doPost(final HttpServletRequest req,
			final HttpServletResponse resp) throws ServletException,
			IOException {

		final String action = req.getParameter("action");
		final String resolution = req.getParameter("resolution");
		final String issueKey = req.getParameter("issue_key");
		final String projectName = req.getParameter("project_name");
		final String issueType = req.getParameter("issue_type");
		final String issueSummary = req.getParameter("summary");
		final String fixVersion = req.getParameter("fix_version");
		final String linkType = req.getParameter("link_type");
		final String timeSpent = req.getParameter("time_spent");
		final String priority = req.getParameter("priority");
		final String assignee = req.getParameter("assignee");

		log.info("\n*** Issue Resolver parameters: ***" + "\n\taction="
				+ action + "\n\tresolution=" + resolution + "\n\tIssue Key="
				+ issueKey + "\n\tProject name=" + projectName
				+ "\n\tIssue type=" + issueType + "\n\tSummary=" + issueSummary
				+ "\n\tfixVersion=" + fixVersion + "\n\tTime Spent="
				+ timeSpent + "\n\tPriority=" + priority + "\n\tassignee="
				+ assignee);

		String result = "";

		final String path = req.getContextPath();

		switch (IssueAction.getValue(action)) {
		case ADD:
			result = addIssue(issueKey, issueType, linkType, issueSummary,
					projectName, priority, path);
			break;
		case ASSIGN:
			result = assignIssue(issueKey, assignee);
			break;
		case RESOLVE:
			result = resolveIssue(issueKey, resolution, timeSpent);
			break;
		case NONE:
			return;
		}

		final PrintWriter writer = resp.getWriter();
		writer.println(result);
		writer.flush();
		writer.close();

	}

	private String assignIssue(final String issueKey, final String assignee) {
		final IssueManager issueManager = ComponentManager.getInstance()
				.getIssueManager();

		final UserUtil userUtil = ComponentManager.getInstance().getUserUtil();
		User user = null;

		for (final User usr : userUtil.getUsers()) {
			if (usr.getName().equals(assignee))
				user = usr;
		}

		if (user == null)
			return "Error! User not found.";

		final MutableIssue missue = issueManager.getIssueObject(issueKey);
		missue.setAssignee(user);

		final JiraAuthenticationContext jiraAuthenticationContext = ComponentManager
				.getComponentInstanceOfType(JiraAuthenticationContext.class);

		final User currentUser = jiraAuthenticationContext.getLoggedInUser();

		ComponentManager
				.getInstance()
				.getIssueManager()
				.updateIssue(currentUser, missue,
						EventDispatchOption.ISSUE_UPDATED, false);

		return "Issue succesfully assigned";
	}

	protected String resolveIssue(final String issueKey,
			final String resolutionName, final String timeSpent)
			throws ServletException, IOException {
		log.info("Start resolving issue");

		final IssueManager issueManager = ComponentManager.getInstance()
				.getIssueManager();
		final Issue issue = issueManager.getIssueObject(issueKey);
		final Status status = issue.getStatusObject();
		if (status.getName().equals(IssueFieldConstants.RESOLVED_STATUS))
			return "Error. Cannot obtain status.";
		final JiraWorkflow workFlow = ComponentManager.getInstance()
				.getWorkflowManager().getWorkflow(issue);
		final ConstantsManager constantsManager = ComponentManager
				.getInstance().getConstantsManager();
		final Collection<Resolution> resolutions = constantsManager
				.getResolutionObjects();
		Resolution resolution = null;

		for (final Resolution res : resolutions) {
			log.debug("Resolution :\t\t " + res.getName());
			if (res.getName().equals(resolutionName)) {
				resolution = res;
				break;
			}
		}

		if (resolution == null) {
			log.warn("Resolution not found");
			return "Error. Cannot obtain resolution.";
		}
		final List<Status> statuses = workFlow.getLinkedStatusObjects();

		Status resolvedStatus = null;
		for (final Status st : statuses)
			if (st.getName().equals(IssueFieldConstants.RESOLVED_STATUS)) {
				resolvedStatus = st;
				break;
			}

		if (resolvedStatus == null) {
			log.warn("No resolved status for this workflow");
			return "This element is already resolved or has no resolved status in its workflow";
		}
		final JiraAuthenticationContext jiraAuthenticationContext = ComponentManager
				.getComponentInstanceOfType(JiraAuthenticationContext.class);
		final User user = jiraAuthenticationContext.getLoggedInUser();

		if (timeSpent != null && !timeSpent.isEmpty()) {
			final long value = parseDate(timeSpent);
			if (value != -1) {
				log.info("Parsed time = " + value);

				final MutableIssue missue = issueManager
						.getIssueObject(issueKey);
				missue.setTimeSpent(value);
				ComponentManager
						.getInstance()
						.getIssueManager()
						.updateIssue(user, missue,
								EventDispatchOption.ISSUE_UPDATED, false);
			} else
				return "Cannot parse spent time. Issue not resolved.";
		}
		ActionDescriptor desc = null;
		for (final ActionDescriptor ad : workFlow.getAllActions())
			if (ad.getName().equals("Resolve Issue"))
				desc = ad;

		final IssueInputParameters iip = new IssueInputParametersImpl();
		iip.setResolutionId(resolution.getId());
		iip.setStatusId(resolvedStatus.getId());

		final TransitionValidationResult tvr = is.validateTransition(user,
				issue.getId(), desc.getId(), iip);
		if (tvr.isValid())
			is.transition(user, tvr);
		else {
			for (final String error : tvr.getErrorCollection()
					.getErrorMessages())
				log.error(error);
			return "A error occured, check log for more info";
		}

		final String script = Scripts.TREE_REFRESH_SCRIPT;

		/*
		 * final String script = Scripts.NODE_REFRESH_SCRIPT.replaceAll(
		 * "ISSUE_KEY", issueKey).replaceAll("ELEMENT_ID", issueKey);
		 */
		return "Issue successfully resolved" + script;
	}

	@SuppressWarnings({ "rawtypes", "unchecked" })
	private String addIssue(final String issueKey, final String issueType,
			final String linkType, final String issueSummary,
			final String projectName, final String priorityName,
			final String path) {
		if (issueSummary == null || issueSummary.isEmpty())
			return "Issue cannot be created, please enter summary";
		final IssueFactory issueFactory = ComponentManager.getInstance()
				.getIssueFactory();
		final ProjectManager projectManager = ComponentManager.getInstance()
				.getProjectManager();
		final ConstantsManager constantsManager = ComponentManager
				.getInstance().getConstantsManager();
		final IssueManager issueManager = ComponentManager.getInstance()
				.getIssueManager();
		final IssueLinkManager issueLinkManager = ComponentManager
				.getInstance().getIssueLinkManager();

		final JiraAuthenticationContext jiraAuthenticationContext = ComponentManager
				.getComponentInstanceOfType(JiraAuthenticationContext.class);

		final IssueLinkTypeManager issueLinkTypeManager = ComponentManager
				.getComponentInstanceOfType(IssueLinkTypeManager.class);

		final User user = jiraAuthenticationContext.getLoggedInUser();

		final MutableIssue issueObject = issueFactory.getIssue();

		IssueType issueTypeObject = null;
		for (final IssueType it : constantsManager.getAllIssueTypeObjects()) {
			if (it.getName().equals(issueType))
				issueTypeObject = it;
		}

		Priority priority = null;
		for (final Priority pr : constantsManager.getPriorityObjects()) {
			if (pr.getName().equals(priorityName))
				priority = pr;
		}

		final IssueLinkType link = issueLinkTypeManager
				.getIssueLinkTypesByName(linkType).iterator().next();

		// Regular Fields
		issueObject.setProjectObject(projectManager
				.getProjectObjByName(projectName));
		issueObject.setIssueTypeObject(issueTypeObject);
		issueObject.setSummary(issueSummary);
		issueObject.setPriorityObject(priority);
		issueObject.setReporter(user);
		issueObject.setAssignee(user);

		final Map params = new HashMap();
		params.put("issue", issueObject);
		final Issue parentIssue = issueManager.getIssueObject(issueKey);
		Issue issue = null;

		String errorMessage = "";
		try {
			issue = issueManager.createIssueObject(user, params);
		} catch (final Exception e) {
			log.error("Error while creating issue " + e.getMessage());
			errorMessage = "Error. Cannot create issue object";
		} finally {
			if (!errorMessage.isEmpty())
				return errorMessage;
		}

		try {
			issueLinkManager.createIssueLink(issue.getId(),
					parentIssue.getId(), link.getId(), 1l, user);
		} catch (final Exception e) {
			log.error("Error while creating link: Exception " + e.getCause());
			errorMessage = "Error! Cannot create issue link.";
		} finally {
			if (!errorMessage.isEmpty()) {
				return errorMessage + "<a href=" + path + "/browse/"
						+ issue.getKey()
						+ ">Click this link to navigate created issue</a>";
			}
		}

		log.info("Adding of the issue finished");

		final String script = Scripts.TREE_REFRESH_SCRIPT;

		// BRANCH_REFRESH_SCRIPT.replaceAll("ISSUE_KEY",
		// issue.getKey()).replaceAll("ELEMENT_ID", issueKey);

		return "New issue added! <a href=" + path + "/browse/" + issue.getKey()
				+ ">Click this link to navigate created issue</a>" + script;
	}

	/**
	 * @param raw
	 * @return a long value of date in seconds or -1 if string is invalid.
	 */
	static long parseDate(final String raw) {
		final Matcher match = pattern.matcher(raw);
		if (!match.find())
			return -1l;
		long accum = 0;
		final String[] strings = raw.split(" ");
		for (int i = 0; i < strings.length; i++) {
			if (strings[i].isEmpty())
				continue;
			final char ident = strings[i].charAt(strings[i].length() - 1);
			long value;
			try {
				value = Long.parseLong(strings[i].substring(0,
						strings[i].indexOf(ident)));
			} catch (final Exception e) {
				log.warn("Parsing error. Cannot parse " + strings[i]);
				accum = -1;
				break;
			}
			if (value < 0) {
				log.warn("Negative time found. Parsing Error.");
				accum = -1;
				break;
			}
			switch (ident) {
			case 'm':
				accum += value * 60;
				break;
			case 'h':
				accum += value * 60 * 60;
				break;
			case 'd':
				accum += value * 60 * 60 * 8;
				break;
			case 'w':
				accum += value * 60 * 60 * 8 * 5;
				break;
			}
		}
		return accum;
	}
}